﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GraphicsFramework.Drawing
{
    public class GraphicBase : Base
    {

        private float angle;

        #region Constructor and Cloning

        public GraphicBase(Enum.EnumDrawingType type, string description, Enum.EnumMouseClicks click)
            : base(type, description, click)
        {
            angle = 0;
        }

        public virtual GraphicBase Clone()
        {
            GraphicBase graphic = new GraphicBase(Type, Description, MouseClick);

            Clone(graphic);

            return graphic;
        }

        public void Clone(GraphicBase graphic)
        {
            List<Entity.Point2F> points = new List<Entity.Point2F>();

            foreach (Entity.Point2F point in this.points)
            {
                points.Add(point.Clone());
            }

            graphic.points = points;

            List<Entity.Point2F> original = new List<Entity.Point2F>();

            foreach (Entity.Point2F point in this.original)
            {
                original.Add(point.Clone());
            }

            graphic.Center = Center.Clone();
            graphic.angle = angle;
            graphic.original = original;
            graphic.properties = properties.Clone();
            graphic.textproperties = textproperties.Clone();
        }

        #endregion

        #region Selection

        public virtual bool SelectByPoint(GraphicsFramework graphics, Entity.Point2F point, bool multiselect)
        {
            //Check if drawing is already selected.
            if (Selected)
            {
                //Try to select the node.
                if (SelectNode(graphics, point, multiselect).Count != 0)
                    return Selected;

            }

            //Check the drawing type.
            switch (Type)
            {
                case Enum.EnumDrawingType.LINE:
                case Enum.EnumDrawingType.POLYLINE:
                    Selected = SelectPolylineByPoint(graphics, point);
                    break;
                case Enum.EnumDrawingType.POLYGON:
                    Selected = SelectPolygonByPoint(graphics, point);
                    break;
                default:
                    break;
            }

            return Selected;
        }

        public bool TestSelectByPoint(GraphicsFramework graphics, Entity.Point2F point)
        {
            switch (Type)
            {
                case Enum.EnumDrawingType.LINE:
                case Enum.EnumDrawingType.POLYLINE:
                    return SelectPolylineByPoint(graphics, point);
                case Enum.EnumDrawingType.POLYGON:
                    return SelectPolygonByPoint(graphics, point);
            }

            return false;
        }

        public void DeleteSelectedNode()
        {
            foreach (int selected in selectednodes)
                points.RemoveAt(selected);
        }

        public bool IsExists(Entity.Point2F node, int precision)
        {
            foreach (Entity.Point2F point in points)
            {
                if (point.IsEqual(node, precision))
                    return true;
            }

            return false;
        }

        public bool AddNode(GraphicsFramework graphics, Entity.Point2F point)
        {
            Entity.Line2F line;
            float tolerance = graphics.GetRelativeThickness(properties) / 2;

            if ((Type == Enum.EnumDrawingType.POLYLINE) || (Type == Enum.EnumDrawingType.LINE))
            {
                for (int x = 1; x < points.Count; x++)
                {
                    line = new Entity.Line2F(points[x], points[x - 1]);
                    if (InsertNode(line, point, x, tolerance, graphics.Precision)) return true;
                }
            }
            else
            {
                for (int x = 1; x <= points.Count; x++)
                {
                    if (x == points.Count)
                    {
                        line = new Entity.Line2F(points[0], points[x - 1]);
                    }
                    else
                    {
                        line = new Entity.Line2F(points[x], points[x - 1]);
                    }

                    if (InsertNode(line, point, x, tolerance, graphics.Precision)) return true;
                }
            }

            return false;
        }

        public virtual bool SelectByRectangle(GraphicsFramework graphics, Entity.Rectangle2F bounds)
        {
            bool sel = true;

            foreach (Entity.Point2F point in points)
            {
                if (!bounds.IsInside(point.X, point.Y))
                {
                    sel = false;
                    break;
                }
            }

            Selected = sel;
            return Selected;
        }

        private List<int> SelectNode(GraphicsFramework graphics, Entity.Point2F point, bool multiselect)
        {
            Entity.Rectangle2F rect;
            float nodesizeX = 5F * graphics.GridValue.X / graphics.Gridsize;
            float nodesizeY = 5F * graphics.GridValue.Y / graphics.Gridsize;

            if (!multiselect)
                selectednodes.Clear();

            for (int x = 0; x < points.Count; x++)
            {
                rect = new Entity.Rectangle2F(new Entity.Point2F(points[x].X - nodesizeX, points[x].Y - nodesizeY),
                                              new Entity.Point2F(points[x].X + nodesizeX, points[x].Y + nodesizeY));

                if (selectednodes.IndexOf(x) == -1)
                {
                    if (rect.IsInside(point.X, point.Y))
                    {
                        selectednodes.Add(x);
                    }
                }
                else
                {
                    if (rect.IsInside(point.X, point.Y))
                    {
                        selectednodes.Add(x);
                        selectednodes.Remove(x);
                    }
                }

            }

            return selectednodes;
        }

        private bool InsertNode(Entity.Line2F line, Entity.Point2F point, int index, float tolerance, int precision)
        {
            Entity.Point2F intersect;
            intersect = line.GetIntersection(point, tolerance);

            if (intersect != null)
                if (!IsExists(intersect, precision))
                {
                    points.Insert(index, intersect);
                    return true;
                }

            return false;
        }

        private bool SelectPolylineByPoint(GraphicsFramework graphics, Entity.Point2F point)
        {
            Entity.Line2F line;
            Entity.Point2F intersect;

            float tolerance;

            tolerance = graphics.GetRelativeThickness(properties) / 2;

            if ((Type == Enum.EnumDrawingType.POLYLINE) || (Type == Enum.EnumDrawingType.LINE))
            {
                for (int x = 1; x < points.Count; x++)
                {
                    line = new Entity.Line2F(points[x], points[x - 1]);
                    intersect = line.GetIntersection(point, tolerance);
                    if (intersect != null) return true;
                }
            }
            else
            {
                for (int x = 1; x <= points.Count; x++)
                {
                    if (x == points.Count)
                    {
                        line = new Entity.Line2F(points[0], points[x - 1]);
                        intersect = line.GetIntersection(point, tolerance);
                        if (intersect != null) return true;
                    }
                    else
                    {
                        line = new Entity.Line2F(points[x], points[x - 1]);
                        intersect = line.GetIntersection(point, tolerance);
                        if (intersect != null) return true;
                    }
                }
            }

            return false;
        }

        private bool SelectPolygonByPoint(GraphicsFramework graphics, Entity.Point2F point)
        {
            Entity.Rectangle2F bounds = GetBounds();
            Entity.Point2F intersection;
            Entity.Line2F line;
            Entity.Line2F compare;

            int left = 0;
            int right = 0;

            for (int x = 1; x <= points.Count; x++)
            {
                if (x != points.Count)
                {
                    line = new Entity.Line2F(points[x], points[x - 1]);
                }
                else
                {
                    line = new Entity.Line2F(points[0], points[x - 1]);
                }

                compare = new Entity.Line2F(new Entity.Point2F(bounds.Point1.X, point.Y), new Entity.Point2F(bounds.Point2.X, point.Y));
                intersection = line.GetIntersection(compare);

                if (intersection != null)
                {
                    if (point.X > intersection.X)
                    {
                        left++;
                    }
                    else
                    {
                        right++;
                    }
                }
            }

            if (((left % 2) != 0) && ((right % 2) != 0))
                return true;

            return false;
        }

        #endregion

        #region Drawing

        protected void UpdateDrawing()
        {
            original.Clear();

            foreach (Entity.Point2F point in this.points)
            {
                original.Add(point.Clone());
            }
        }

        protected void UpdateCenter()
        {
            Entity.Rectangle2F bounds = GetBounds();

            if (bounds != null)
                this.Center = new Entity.Point2F((bounds.Point1.X + bounds.Point2.X) / 2,
                                                           (bounds.Point1.Y + bounds.Point2.Y) / 2);
        }

        public virtual void UpdateGuide(List<Entity.Point2F> mousedown, Entity.Point2F mousemove)
        {
        }

        public virtual void OnRender(GraphicsFramework graphics)
        {
            switch (Type)
            {
                case Enum.EnumDrawingType.LINE:
                case Enum.EnumDrawingType.POLYLINE:
                    DrawPolyline(graphics);
                    break;
                case Enum.EnumDrawingType.POLYGON:
                    DrawPolygon(graphics);
                    break;
                default:
                    break;
            }
        }

        public virtual void OnRenderNode(GraphicsFramework graphics)
        {
            for (int x = 0; x < points.Count; x++)
            {
                graphics.BasicRectangle(Property.DefaultProperty.Node, graphics.ToCoord(points[x]), 5, 5);

            }

            for (int x = 0; x < points.Count; x++)
            {
                graphics.BasicRectangle(Property.DefaultProperty.Node, graphics.ToCoord(original[x]), 5, 5);

            }

            foreach (int node in selectednodes)
            {
                graphics.BasicRectangle(Property.DefaultProperty.SelectedUnscaled, graphics.ToCoord(points[node]), 5, 5);
            }
        }

        private void DrawPolyline(GraphicsFramework graphics)
        {
            if (IsConstructionLine)
            {
                if (points.Count > 1)
                {
                    Property.DrawProperty prop = Property.DefaultProperty.ConstructionLine.Clone();
                    prop.ScaleOutline = properties.ScaleOutline;
                    prop.LineWidth = properties.LineWidth;
                    graphics.DrawPolyline(prop, points);
                }
            }
            else
            {
                if (points.Count > 1)
                    graphics.DrawPolyline(properties, points);
            }
        }

        private void DrawPolygon(GraphicsFramework graphics)
        {
            if (points.Count > 2)
            {
                if (IsConstructionLine)
                    graphics.DrawPolygon(Property.DefaultProperty.ConstructionLine, points);
                else
                {
                    graphics.DrawPolygon(properties, points);

                    if (original.Count != 0)
                        graphics.DrawPolygon(Property.DefaultProperty.ConstructionLine, original);
                }
            }
            else if (points.Count > 1)
            {
                graphics.DrawPolyline(properties, points);
            }
        }

        #endregion

        #region Transformation

        public virtual void Move(Entity.Point2F value, Entity.Point2F snapvalue)
        {
            if (selectednodes.Count != 0)
            {
                //Move the selected points
                foreach (int node in selectednodes)
                    points[node].Move(value);

                //Need to update the original
                UpdateDrawing();
            }
            else
            {
                //Move the points
                foreach (Entity.Point2F point in points)
                    point.Move(value);

                //Move the original
                foreach (Entity.Point2F point in original)
                    point.Move(value);

                Center.Move(value);

            }
        }

        public virtual void FlipHorizontal(Entity.Point2F center)
        {
            //Flip the points
            foreach (Entity.Point2F point in points)
                point.X = center.X - (point.X - center.X);

            //Flip the center
            if (!this.Center.IsEqual(center))
                this.Center.X = center.X - (this.Center.X - center.X);
        }

        public virtual void FlipVertical(Entity.Point2F center)
        {
            //Flip the points
            foreach (Entity.Point2F point in points)
                point.Y = center.Y - (point.Y - center.Y);

            //Fli the center
            if (!this.Center.IsEqual(center))
                this.Center.Y = center.Y - (this.Center.Y - center.Y);
        }

        public virtual void Rotate(Entity.Point2F center, float angle, int precision)
        {
            //Increment the angle
            this.angle += angle;

            //Check angle limits
            if (this.angle >= 360) this.angle -= 360;
            if (this.angle < 0) this.angle += 360;

            //Rotate the center
            if (!this.Center.IsEqual(center))
            {
                //Rotate the points
                for (int count = 0; count < original.Count; count++)
                    points[count] = RotatePoint(points[count], center, angle, precision);

                Entity.Point2F diff = RotatePoint(this.Center, center, angle, precision);

                //Move the original
                Entity.Point2F move = new Entity.Point2F(diff.X - this.Center.X, diff.Y - this.Center.Y);
                foreach (Entity.Point2F point in original)
                    point.Move(move);

                this.Center = diff;
            }
            else
            {
                //Rotate the points
                for (int count = 0; count < original.Count; count++)
                    points[count] = RotatePoint(original[count], center, this.angle, precision);

                this.Center = RotatePoint(this.Center, center, this.angle, precision);
            }

        }

        public virtual void RotateLeft(Entity.Point2F center, int precision)
        {
            //Call the rotate function with 90 degrees as parameter
            Rotate(center, 90, precision);
        }

        public virtual void RotateRight(Entity.Point2F center, int precision)
        {
            //Call the rotate function with -90 degrees as parameter
            Rotate(center, -90, precision);
        }

        private Entity.Point2F RotatePoint(Entity.Point2F point, Entity.Point2F center, float angle, int precision)
        {
            float radian = DegressToRadians(angle);

            Entity.Point2F rotated = new Entity.Point2F((float)(center.X + Math.Cos(radian) * (point.X - center.X) - Math.Sin(radian) * (point.Y - center.Y)),
                                                        (float)(center.Y + Math.Sin(radian) * (point.X - center.X) + Math.Cos(radian) * (point.Y - center.Y)));

            rotated.X = (float)Math.Round(rotated.X, precision + 3);
            rotated.Y = (float)Math.Round(rotated.Y, precision + 3);

            return rotated;
        }

        private float DegressToRadians(float angle)
        {
            return (float) (angle * Math.PI / 180);
        }

        #endregion

        #region Supports

        public Entity.Rectangle2F GetOriginalBounds()
        {
            if (original.Count != 0)
            {
                Entity.Point2F max = original[0].Clone();
                Entity.Point2F min = original[0].Clone();

                foreach (Entity.Point2F point in original)
                {
                    if (max.X < point.X) max.X = point.X;
                    if (min.X > point.X) min.X = point.X;

                    if (max.Y < point.Y) max.Y = point.Y;
                    if (min.Y > point.Y) min.Y = point.Y;
                }

                return new Entity.Rectangle2F(min, max);
            }

            return null;
        }

        public override Entity.Rectangle2F GetBounds()
        {
            if (points.Count != 0)
            {
                Entity.Point2F max = points[0].Clone();
                Entity.Point2F min = points[0].Clone();

                foreach (Entity.Point2F point in points)
                {
                    if (max.X < point.X) max.X = point.X;
                    if (min.X > point.X) min.X = point.X;

                    if (max.Y < point.Y) max.Y = point.Y;
                    if (min.Y > point.Y) min.Y = point.Y;
                }

                return new Entity.Rectangle2F(min, max);
            }

            return null;
        }

        #endregion

        #region Properties

        public float Angle
        {
            get { return angle; }
            set
            {
                angle = value;
            }
        }

        #endregion

    }
}
